<!doctype html>
<meta charset=utf-8>
<title>Auto-running execCommand() tests</title>
<link rel=stylesheet href=tests.css>
<p>See the <a href=editing.html#tests>Tests</a> section of the specification
for documentation.

<h1>Table of Contents</h1>
<ul>
</ul>

<button onclick="for (var command in tests) runTests(command)">Run all tests</button>

<script src=implementation.js></script>
<script src=tests.js></script>
<script>
"use strict";
// Set up all the HTML automatically so I can add new commands to test without
// copy-pasting in five places
(function() {
	var toc = document.querySelector("ul");
	for (var command in tests) {
		var li = document.createElement("li");
		li.innerHTML = "<a href=#" + command + ">" + command + "</a>";
		toc.appendChild(li);

		var div = document.createElement("div");
		div.id = command;
		var supported = "";
		if (command != "multitest") {
			try {
				supported = document.queryCommandSupported(command)
					? "<span style=color:green>Supported</span>"
					: "<span style=color:red>Not supported</span>";
			} catch (e) {
				supported = "<strong style=color:red>Exception for queryCommandSupported</strong>";
			}
		}
		div.innerHTML = "<h1>" + command + "</h1>"
			+ supported
			+ " <button onclick=\"runTests('" + command + "')\">Run tests</button>"
			+ (command in notes ? "<p>" + notes[command] : "")
			+ "<table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>"
			+ (doubleTestingCommands.indexOf(command) != -1 ? "<table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>" : "")
			+ "<p><label>New test input: <input></label>"
			+ (command in defaultValues ? "<label>New test value: <input></label>" : "")
			+ "<button onclick=\"addTest('" + command + "')\">Add test</button>";
		document.body.appendChild(div);
	}
})();

function runTests(command) {
	var runTestsButton = document.querySelector("#" + command + " button");

	if (runTestsButton.textContent != "Run tests") {
		return;
	}

	runTestsButton.parentNode.removeChild(runTestsButton);

	// Make sure styleWithCss is always reset to false at the start of a test
	// run
	myExecCommand("stylewithcss", false, "false");
	try { document.execCommand("stylewithcss", false, "false") } catch(e) {}

	var addTestButton = document.querySelector("#" + command + " button");
	var inputs = document.getElementById(command).getElementsByTagName("input");
	for (var i = 0; i < tests[command].length; i++) {
		// This code actually focuses and clicks everything because for some
		// reason, anything else doesn't work in IE9 . . .
		//
		// In case the input contains something unpleasant like newlines, we
		// smuggle in the string as a data attribute, not just the value.  This
		// is probably unnecessarily magic.
		if (command == "multitest") {
			inputs[0].value = JSON.stringify(tests[command][i]);
			inputs[0].setAttribute("data-input", JSON.stringify(tests[command][i]));
		} else if (typeof tests[command][i] == "string") {
			inputs[0].value = tests[command][i];
			inputs[0].setAttribute("data-input", tests[command][i]);
			if (inputs.length == 2) {
				inputs[1].value = defaultValues[command];
				inputs[1].setAttribute("data-input", defaultValues[command]);
			}
		} else {
			inputs[0].value = tests[command][i][1];
			inputs[0].setAttribute("data-input", tests[command][i][1]);
			inputs[1].value = tests[command][i][0];
			inputs[1].setAttribute("data-input", tests[command][i][0]);
		}
		inputs[0].focus();
		addTestButton.click();
	}
	for (var i = 0; i < inputs.length; i++) {
		inputs[i].value = "";
	}

	document.querySelector("#" + command).scrollIntoView();
}

function addTest(command) {
	var doubleTesting = doubleTestingCommands.indexOf(command) != -1;

	// I tried to feature-detect styleWithCSS, but it was too much of a pain,
	// with Firefox randomly throwing exceptions all over the place.  So I'm
	// just browser-sniffing here.
	if (navigator.userAgent.indexOf("Opera") != -1
	|| navigator.userAgent.indexOf("MSIE") != -1) {
		doubleTesting = false;
	}

	var tr = doSetup("#" + command + " > table", 0);

	var test;
	var inputs = document.getElementById(command).getElementsByTagName("input");

	if (inputs.length == 1) {
		if (inputs[0].hasAttribute("data-input")) {
			test = inputs[0].getAttribute("data-input");
		} else {
			test = inputs[0].value;
		}
	} else {
		if (inputs[0].hasAttribute("data-input")) {
			test = [inputs[1].getAttribute("data-input"), inputs[0].getAttribute("data-input")];
			inputs[1].removeAttribute("data-input");
		} else {
			test = [inputs[1].value, inputs[0].value];
		}
	}
	inputs[0].removeAttribute("data-input");

	// Firefox refuses to do anything for hiliteColor unless styleWithCSS is
	// true.  In other cases where there's supposed to be no effect, we make
	// styleWithCss false.
	var normalizedTest = normalizeTest(command, test, command == "hilitecolor");

	doInputCell(tr, normalizedTest, command);
	doSpecCell(tr, normalizedTest, command);
	doBrowserCell(tr, normalizedTest, command);
	doSameCell(tr);

	if (doubleTesting) {
		var tr = doSetup("#" + command + " > table", 1);

		normalizedTest = normalizeTest(command, test, true);

		doInputCell(tr, normalizedTest, command);
		doSpecCell(tr, normalizedTest, command);
		doBrowserCell(tr, normalizedTest, command);
		doSameCell(tr);
	}

	doTearDown(command);
}

function doBrowserCell(tr, test, command) {
	var browserCell = document.createElement("td");
	tr.appendChild(browserCell);
	if (!document.querySelector("#browser-checkbox").checked) {
		browserCell.textContent = "(skipped)";
		return;
	}

	try {
		var points = setupCell(browserCell, test[0]);

		var testDiv = browserCell.firstChild;
		// Work around weird Firefox bug:
		// https://bugzilla.mozilla.org/show_bug.cgi?id=649138
		document.body.appendChild(testDiv);
		setSelection(points[0], points[1], points[2], points[3]);
		testDiv.contentEditable = "true";
		testDiv.spellcheck = false;

		if (command != "multitest") {
			try { var beforeIndeterm = document.queryCommandIndeterm(command) }
			catch(e) { beforeIndeterm = "Exception" }
			try { var beforeState = document.queryCommandState(command) }
			catch(e) { beforeState = "Exception" }
			try { var beforeValue = document.queryCommandValue(command) }
			catch(e) { beforeValue = "Exception" }
		}

		for (var i = 1; i < test.length; i++) {
			if (test[i][0] === "stylewithcss") {
				// try/catch to avoid an exception in IE
				try { document.execCommand(test[i][0], false, test[i][1]) } catch (e) {}
			} else {
				document.execCommand(test[i][0], false, test[i][1]);
			}
		}

		if (command != "multitest") {
			try { var afterIndeterm = document.queryCommandIndeterm(command) }
			catch(e) { afterIndeterm = "Exception" }
			try { var afterState = document.queryCommandState(command) }
			catch(e) { afterState = "Exception" }
			try { var afterValue = document.queryCommandValue(command) }
			catch(e) { afterValue = "Exception" }
		}

		testDiv.contentEditable = "inherit";
		testDiv.removeAttribute("spellcheck");
		var compareDiv1 = testDiv.cloneNode(true);

		if (getSelection().rangeCount) {
			addBrackets(getSelection().getRangeAt(0));
		}
		browserCell.insertBefore(testDiv, browserCell.firstChild);

		if (!browserCell.childNodes.length == 2) {
			throw "The cell didn't have two children.  Did something spill outside the test div?";
		}

		compareDiv1.normalize();
		// Sigh, Gecko is crazy
		var treeWalker = document.createTreeWalker(compareDiv1, NodeFilter.SHOW_ELEMENT, null, null);
		while (treeWalker.nextNode()) {
			var remove = [].filter.call(treeWalker.currentNode.attributes, function(attrib) {
				return /^_moz_/.test(attrib.name) || attrib.value == "_moz";
			});
			for (var i = 0; i < remove.length; i++) {
				treeWalker.currentNode.removeAttribute(remove[i].name);
			}
		}
		var compareDiv2 = compareDiv1.cloneNode(false);
		compareDiv2.innerHTML = compareDiv1.innerHTML;
		if (!compareDiv1.isEqualNode(compareDiv2)
		&& compareDiv1.innerHTML != compareDiv2.innerHTML) {
			throw "DOM does not round-trip through serialization!  "
				+ compareDiv1.innerHTML + " vs. " + compareDiv2.innerHTML;
		}
		if (!compareDiv1.isEqualNode(compareDiv2)) {
			throw "DOM does not round-trip through serialization (although innerHTML is the same)!  "
				+ compareDiv1.innerHTML;
		}

		if (browserCell.firstChild.attributes.length) {
			throw "Wrapper div has attributes!  " +
				browserCell.innerHTML.replace(/<div><\/div>$/, "");
		}

		if (document.body.hasAttribute("bgcolor")) {
			var bgColor = document.body.getAttribute("bgcolor");
			document.body.removeAttribute("bgcolor");
			throw 'bgcolor="' + bgColor +'" added to body!';
		}

		browserCell.lastChild.textContent = browserCell.firstChild.innerHTML;
		if (command != "multitest") {
			browserCell.lastChild.appendChild(queryOutputHelper(
				beforeIndeterm, beforeState, beforeValue,
				afterIndeterm, afterState, afterValue,
				command, test[2][1]));
		}
	} catch (e) {
		browserCellException(e, testDiv, browserCell);
	}
}
</script>
